import { Vec3 } from 'cc';
import { nUtils } from './nUtils';

const geometry = {
    faces: [],
    vertices: [],
    centroid: [],
}

export class Builder {
    /**
     * @Date: 2022-02-1 21:37:07
     * @LastEditors: iwae
     * @description: 初始化Navmesh数据
     * @param {JsonAsset} asset 传输Json数据
     */
    static buildZoneByJson(json) {
        this.cleanGeometry();
        const v = json.vertices;
        const f = json.faces;
        for (let i = 0; i < v.length; i += 3) {
            geometry.vertices.push(new Vec3(v[i], v[i + 1], v[i + 2]));
        };
        for (let i = 0; i < f.length / 3; i++) {
            this.computeCentroids( f[i * 3], f[i * 3 + 1], f[i * 3 + 2])
        };
        const zone = this.buildZone(geometry);
        return zone;
    }

    /**
     * @Date: 2022-02-20 19:49:06
     * @LastEditors: iwae
     * @description: 清除geometry 数据，防止污染
     */
    static cleanGeometry(){
        geometry.vertices.length=0;
        geometry.faces.length=0;
        geometry.centroid.length=0;
    }

    static buildZone(geometry) {
        const navmesh = this.buildNavMesh(geometry);
        const zone = this.groupNavMesh(navmesh);
        return zone;
    }

    static buildNavMesh(geometry) {
        // Prepare geometry
        nUtils.mergeVertices(geometry);
        const buildNavMesh = this.buildPolygonsFromGeometry(geometry);
        return buildNavMesh;
    };

    /**
     * @Date: 2022-02-20 19:49:38
     * @LastEditors: iwae
     * @description: 计算中心点和面
     * @param {*} a
     * @param {*} b
     * @param {*} c
     */
    static computeCentroids(a, b, c) {
        const centroid = new Vec3()
        Vec3.add(centroid, geometry.vertices[b], geometry.vertices[a]);
        Vec3.add(centroid, geometry.vertices[c], centroid);
        Vec3.multiplyScalar(centroid, centroid, 1 / 3)
        geometry.faces.push(new Array(a, b, c))
        geometry.centroid.push(centroid)
    };

    static buildPolygonsFromGeometry(geometry) {
        const polygons = [];
        const vertices = geometry.vertices;
        // Convert the faces into a custom format that supports more than 3 vertices
        for (let i = 0, len = geometry.faces.length; i < len; i++) {
            polygons.push({
                vertexIds: geometry.faces[i],
                centroid: geometry.centroid[i],
                neighbours: []
            });
        }
        const navMesh = {
            polygons: polygons,
            vertices: vertices,
        };
        // Build a list of adjacent polygons
        for (let i = 0, len = polygons.length; i < len; i++) {
            const polygon = polygons[i];
            this.buildPolygonNeighbours(polygon, navMesh);
        }
        return navMesh;
    };
    static groupNavMesh(navmesh) {
        const zone = {
            vertices: null,
            groups: null
        };
        zone.vertices = navmesh.vertices;
        const groups = this.buildPolygonGroups(navmesh);
        zone.groups = [];
        const findPolygonIndex = function (group, p) {
            for (let i = 0; i < group.length; i++) {
                if (p === group[i])
                    return i;
            }
        };
        for (let i = 0, len = groups.length; i < len; i++) {
            const group = groups[i];
            const newGroup = [];
            for (let j = 0, len2 = group.length; j < len2; j++) {
                const p = group[j];
                const neighbours = [];
                const portals = [];
                for (let z = 0, len3 = p.neighbours.length; z < len3; z++) {
                    const n = p.neighbours[z];
                    neighbours.push(findPolygonIndex(group, n));
                    portals.push(this.getSharedVerticesInOrder(p, n));
                }
                p.centroid.x = nUtils.roundNumber(p.centroid.x, 2);
                p.centroid.y = nUtils.roundNumber(p.centroid.y, 2);
                p.centroid.z = nUtils.roundNumber(p.centroid.z, 2);
                newGroup.push({
                    id: findPolygonIndex(group, p),
                    neighbours: neighbours,
                    vertexIds: p.vertexIds,
                    centroid: p.centroid,
                    portals: portals
                });
            }
            zone.groups.push(newGroup);
        }
        return zone;
    };
    static buildPolygonNeighbours(polygon, navMesh) {
        polygon.neighbours = [];
        // All other nodes that contain at least two of our vertices are our neighbours
        for (let i = 0, len = navMesh.polygons.length; i < len; i++) {
            if (polygon === navMesh.polygons[i]) continue;
            // Don't check polygons that are too far, since the intersection tests take a long time
            if (Vec3.squaredDistance(polygon.centroid, navMesh.polygons[i].centroid) > 100 * 100) continue;
            const matches = this.arrayIntersect(polygon.vertexIds, navMesh.polygons[i].vertexIds);
            if (matches.length >= 2) {
                polygon.neighbours.push(navMesh.polygons[i]);
            }
        }
    };
    static getSharedVerticesInOrder(a, b) {
        const aList = a.vertexIds;
        const a0 = aList[0], a1 = aList[1], a2 = aList[2];
        const bList = b.vertexIds;
        const shared0 = bList.includes(a0);
        const shared1 = bList.includes(a1);
        const shared2 = bList.includes(a2);
        // it seems that we shouldn't have an a and b with <2 shared vertices here unless there's a bug
        // in the neighbor identification code, or perhaps a malformed input geometry; 3 shared vertices
        if (shared0 && shared1 && shared2) {
            return Array.from(aList);
        } else if (shared0 && shared1) {
            return [a0, a1];
        } else if (shared1 && shared2) {
            return [a1, a2];
        } else if (shared0 && shared2) {
            return [a2, a0]; // this ordering will affect the string pull algorithm later, not clear if significant
        } else {
            console.warn("Error,neighbors with <2 shared vertices found.");
            return [];
        }
    }

    static buildPolygonGroups(navigationMesh) {
        const polygons = navigationMesh.polygons;
        const polygonGroups = [];
        let groupCount = 0;
        function spreadGroupId(polygon) {
            for (let i = 0, len = polygon.neighbours.length; i < len; i++) {
                const neighbour = polygon.neighbours[i];
                if (neighbour.group == undefined) {
                    neighbour.group = polygon.group;
                    spreadGroupId(neighbour);
                }
            }
        };
        for (let i = 0, len = polygons.length; i < len; i++) {
            const polygon = polygons[i];
            if (polygon.group == undefined) {
                polygon.group = groupCount++;
                spreadGroupId(polygon);
            }
            if (!polygonGroups[polygon.group])
                polygonGroups[polygon.group] = [];
            polygonGroups[polygon.group].push(polygon);
        }
        return polygonGroups;
    };


    static arrayIntersect(...params) {
        let i, shortest, nShortest, n, len, ret = [], obj = {}, nOthers;
        nOthers = params.length - 1;
        nShortest = params[0].length;
        shortest = 0;
        for (i = 0; i <= nOthers; i++) {
            n = params[i].length;
            if (n < nShortest) {
                shortest = i;
                nShortest = n;
            }
        }
        for (i = 0; i <= nOthers; i++) {
            n = (i === shortest) ? 0 : (i || shortest); //Read the shortest array first. Read the first array instead of the shortest
            len = params[n].length;
            for (let j = 0; j < len; j++) {
                let elem = params[n][j];
                if (obj[elem] === i - 1) {
                    if (i === nOthers) {
                        ret.push(elem);
                        obj[elem] = 0;
                    }
                    else {
                        obj[elem] = i;
                    }
                }
                else if (i === 0) {
                    obj[elem] = 0;
                }
            }
        }
        return ret;
    }

}